package org.putrpctorest.expression;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import org.apache.cxf.helpers.DOMUtils;
import org.putrpctorest.PutRpcToRestError;
import org.w3c.dom.Element;

public class MethodCallParser extends ExpressionParser {
	
	private final static String REST_ENTRY_POINT_NS = "http://www.example.org/rest-expressions";


	static {

		ExpressionFactory.getInstance().registerParser(
				new MethodCallParser());
	}

	public AbstractExpression parseRouter(String id, Element config) {
		MethodCall methodCall = new MethodCall(id);

		verifiyConfig(config);

		Element methodElement = DOMUtils.getFirstChildWithName(config, REST_ENTRY_POINT_NS,
				"java-method");

		String targetInstanceClass = methodElement
				.getAttribute("target-instance-class");
		Object targetInstance = createInstance(targetInstanceClass);
		methodCall.setTargetObject(targetInstance);
		methodCall.setJavaMethod(getMethod(targetInstanceClass,
				methodElement.getAttribute("method")));

		Element paramCorresps = DOMUtils.getFirstChildWithName(config, REST_ENTRY_POINT_NS,
				"parameter-correspondence");

		Map<Integer, String> corresp = new HashMap<Integer, String>();

		Element paramCorresp = DOMUtils.getFirstChildWithName(paramCorresps,
				REST_ENTRY_POINT_NS, "parameter");
		while (paramCorresp != null) {
			corresp.put(Integer.parseInt(paramCorresp.getAttribute("index")),
					paramCorresp.getAttribute("name"));
			paramCorresp = DOMUtils.getNextElement(paramCorresp);
		}

		String[] parasCorrsArray = new String[corresp.size()];

		for (Map.Entry<Integer, String> entry : corresp.entrySet()) {
			parasCorrsArray[entry.getKey()] = entry.getValue();
		}

		methodCall.setParamCorrespondence(parasCorrsArray);

		return methodCall;
	}

	private Method getMethod(String className, String methodDescriptor) {
		String[] methodDescriptorParts = methodDescriptor.split(",");

		Class<?>[] paramTypes = new Class<?>[methodDescriptorParts.length - 1];

		for (int i = 1; i < methodDescriptorParts.length; i++) {
			paramTypes[i - 1] = getClass(methodDescriptorParts[i]);
		}
		try {
			return getClass(className).getDeclaredMethod(
					methodDescriptorParts[0], paramTypes);
		} catch (Exception e) {
			throw new PutRpcToRestError("erro getting method "
					+ methodDescriptor + " for class " + className + ". " +e,e);
		}
	}

	private Object createInstance(String targetInstanceClass) {
		Class<?> clazz;
		try {
			clazz = getClass(targetInstanceClass);
			return clazz.newInstance();
		} catch (Exception e) {
			throw new PutRpcToRestError(
					"Could not create a instance for class "
							+ targetInstanceClass
							+ " to configure a method call router! " + e, e);
		}
	}

	private Class<?> getClass(String className) {
		try {
			return Class.forName(className);
		} catch (ClassNotFoundException e) {
			throw new PutRpcToRestError("Could not find class " + className
					+ " to configure a method call router!");
		}
	}

	private void verifiyConfig(Element config) {

		Element javaMethodEl = DOMUtils.getFirstChildWithName(config, REST_ENTRY_POINT_NS,
				"java-method");

		if (javaMethodEl == null) {
			throw new PutRpcToRestError(
					"could not find java-method element inside config!");
		}
		if (!(hasProperty(javaMethodEl, "target-instance-id") || hasProperty(
				javaMethodEl, "target-instance-class"))) {
			throw new PutRpcToRestError(
					"Element java-method must have a target-instance-id or target-instance-class!");
		}
		if (!hasProperty(javaMethodEl, "method")) {
			throw new PutRpcToRestError(
					"method attribute for element java-method must be informed!");
		}
		Element paramCorresps = DOMUtils.getFirstChildWithName(config, REST_ENTRY_POINT_NS,
				"parameter-correspondence");
		if (paramCorresps == null) {
			throw new PutRpcToRestError(
					"could not find parameter-correspondence element inside config!");
		}

	}

	private boolean hasProperty(Element element, String property) {
		String propValue = element.getAttribute(property);
		if (propValue == null || propValue.trim().equals("")) {
			return false;
		} else {
			return true;
		}
	}

	public String getType() {
		return "method-call";
	}

}
